package org.md.budzik.problems;

import java.math.BigInteger;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.Stack;
import java.util.StringTokenizer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import android.content.ContentResolver;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;

/**
 * Klasa reprezentująca zadanie z MD.
 * @author Wiezzel, Saephir
 *
 */
public class Problem {
	
	private static Random random = new Random();
	
	private int number;
	private String text;
	private Map<String,Integer> variables;
	private Expression formula;
	
	/**
	 * Pobierz losowy problem z bazy danych.
	 * @param context
	 * @return
	 * @throws ParseException
	 */
	public static Problem getRandomProblem(Context context) throws ParseException {
		
		ContentResolver resolver = context.getContentResolver();
		Uri uri = ProblemTemplateProvider.CONTENT_URI;
		Cursor cursor = resolver.query(uri, null, null, null, null);
		int offset = random.nextInt(cursor.getCount());
		cursor.moveToPosition(offset);
		
		int id_index = cursor.getColumnIndexOrThrow(ProblemTemplateProvider.Constants._ID);
		int text_index = cursor.getColumnIndexOrThrow(ProblemTemplateProvider.Constants.TEXT);
		int variables_index = cursor.getColumnIndexOrThrow(ProblemTemplateProvider.Constants.VARIABLES);
		int formula_index = cursor.getColumnIndexOrThrow(ProblemTemplateProvider.Constants.FORMULA);
		
		int number = cursor.getInt(id_index);
		String text = cursor.getString(text_index);
		String variables = cursor.getString(variables_index);
		String formula = cursor.getString(formula_index);
		
		cursor.close();
		
		return new Problem(number, text, variables, formula);
	}
	
	/**
	 * Parsuj zmienne do zadania.
	 */
	private static Map<String,Integer> parseVariables(String s) throws ParseException {
		StringTokenizer tkn = new StringTokenizer(s, ",");
		Map<String,Integer> map = new HashMap<String,Integer>();
		Pattern pattern = Pattern.compile("\\[(\\w+):([a-zA-Z0-9+\\-*/%^! ]+);([a-zA-Z0-9+\\-*/%^! ]+)\\]");
		while (tkn.hasMoreTokens()) {
			String token = tkn.nextToken();
			Matcher matcher = pattern.matcher(token);
			if (!matcher.matches())
				throw new ParseException(token + " nie jest poprawnie zdefiniowaną zmienną");
			String name = matcher.group(1);
			String left = matcher.group(2);
			String right = matcher.group(3);
			int lval = parseExpression(left).value(map).intValue();
			int rval = parseExpression(right).value(map).intValue();
			if (lval >= rval)
				throw new ParseException("Niepoprany zakres wartości: (" + lval + ";" + rval + ")");
			map.put(name, random.nextInt(rval - lval) + lval);
		}
		return map;
	}
	
	/**
	 * Parsuj wyrażenie ONP na wynik.
	 * @param s
	 * @return
	 */
	private static Expression parseExpression(String s) {
		StringTokenizer tkn  = new StringTokenizer(s);
		Stack<Expression> st = new Stack<Expression>();
		while (tkn.hasMoreTokens()) {
			String token = tkn.nextToken();
			if (token.equals("!"))
				st.push(new Factorial(st.pop()));
			else if (token.equals("disor"))
				st.push(new Disorder(st.pop()));
			else if (token.equals("catal"))
				st.push(new CatalanNumber(st.pop()));
			else if (token.equals("bell"))
				st.push(new BellNumber(st.pop()));
			else if (token.equals("+"))
				st.push(new Addition(st.pop(), st.pop()));
			else if (token.equals("-"))
				st.push(new Subtraction(st.pop(), st.pop()));
			else if (token.equals("*"))
				st.push(new Multiplication(st.pop(), st.pop()));
			else if (token.equals("/"))
				st.push(new Division(st.pop(), st.pop()));
			else if (token.equals("%"))
				st.push(new Modulo(st.pop(), st.pop()));
			else if (token.equals("^"))
				st.push(new Power(st.pop(), st.pop()));
			else if (token.equals("binom"))
				st.push(new Binomial(st.pop(), st.pop()));
			else if (token.equals("stir1"))
				st.push(new StirlingFirstKind(st.pop(), st.pop()));
			else if (token.equals("stir2"))
				st.push(new StirlingSecondKind(st.pop(), st.pop()));
			else if (token.matches("-?\\d+"))
				st.push(new Constant(new BigInteger(token)));
			else 
				st.push(new Variable(token));
		}
		return st.pop();
	}
	
	/**
	 * 
	 * @param number numer zadania
	 * @param text treść
	 * @param variables zmienne
	 * @param formula formuła do wyliczenia wyniku
	 * @throws ParseException
	 */
	public Problem(int number, String text, String variables, String formula) throws ParseException {
		this.number = number;
		this.text = text;
		this.variables = parseVariables(variables);
		this.formula = parseExpression(formula);
	}
	
	/**
	 * Pobierz numer zadania.
	 * @return
	 */
	public int getNumber() {
		return number;
	}
	
	/**
	 * Pobierz treść zadania.
	 * @return
	 */
	public String getText() {
		return text;
	}
	
	/**
	 * Pobierz kolekcję zmiennych.
	 * @return
	 */
	public Map<String,Integer> getVariables() {
		return variables;
	}
	
	/**
	 * Pobierz poprawne rozwiązanie zadania jako String.
	 * @return
	 * @throws ParseException
	 */
	public String getResult() throws ParseException {
		return formula.value(variables).toString();
	}

	/**
	 * Ciąg zmiennych do zadania podany w postaci tekstowej.
	 * @return
	 */
	public String getVariableString() {
		String returnValue = "";
		for(String zmienna : variables.keySet())
			returnValue += (zmienna+"="+String.valueOf(variables.get(zmienna))+", ");
		return returnValue.substring(0, returnValue.length()-2);
	}
	
}
